using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

namespace BLT {
    public enum MoveDirection { UP, DOWN, LEFT, RIGHT };

    public class Sprite {

        #region Type Definitions
        /// <summary>
        /// The delegate function signature that is used for the collisionAction field
        /// <param name="sprite1">one sprite that is colliding</param>
        /// <param name="sprite2">the other sprite that is colliding</param>
        /// </summary>
        public delegate void collisionActionDelegate(Sprite sprite1, Sprite sprite2);
        #endregion

        #region All the Fields and their getters/setters

        /// <summary>
        /// The sprite's texture (since many Sprites can share a texture, make sure that this only gets used as a reference)
        /// </summary>
        private Texture2D texture = null;
        public Texture2D m_texture {
            get { return texture; }
            set { texture = value; }
        }

        /// <summary>
        /// The upper-left corner of the sprite
        /// </summary>
        private Vector2 position = new Vector2(0, 0);
        public Vector2 m_position {
            get { return position; }
            set { position = value; }
        }

        /// <summary>
        /// The velocity of the sprite
        /// </summary>
        private Vector2 velocity = new Vector2(0, 0);
        public Vector2 m_velocity {
            get { return velocity; }
            set { velocity = value; }
        }

        public float m_velocityX
        {
            get { return velocity.X; }
            set 
            { 
                velocity.X = value;
                if (velocity.X > maxSpeed.X)
                    velocity.X = maxSpeed.X;
            }
        }

        public float m_velocityY
        {
            get { return velocity.X; }
            set 
            { 
                velocity.Y = value;
                if (velocity.Y > maxSpeed.Y)
                    velocity.Y = maxSpeed.Y;
            }
        }

        /// <summary>
        /// How much to adjust Sprite velocity when a movement key is pressed
        /// </summary>
        private float moveSpeed = 0.0f;
        public float m_moveSpeed {
            get { return moveSpeed; }
            set { moveSpeed = value; }
        }        

        /// <summary>
        /// How large the Sprite will be drawn
        /// </summary>
        private Vector2 size = new Vector2(1, 1);
        public Vector2 m_size {
            get { return size; }
            set { size = value; }
        }

        /// <summary>
        /// The maximum speed the Sprite can attain
        /// </summary>
        private Vector2 maxSpeed = new Vector2(0, 0);
        public Vector2 m_maxSpeed {
            get { return maxSpeed; }
            set { maxSpeed = value; }
        }

        /// <summary>
        /// Used for calculating collisions
        /// </summary>
        private float mass = 0.0f;
        public float m_mass {
            get { return mass; }
            set { mass = value; }
        }

        /// <summary>
        /// Whether or not to draw the Sprite when the draw function is called.
        /// </summary>
        private bool visible = true;
        public bool m_visible {
            get { return visible; }
            set { visible = value; }
        }

        /// <summary>
        /// The portion of the texture that should be drawn at the position (with the Sprite's size)
        /// </summary>
        private Rectangle visiblePartOfTexture = new Rectangle(0, 0, 0, 0);
        public Rectangle m_visiblePartOfTexture {
            get { return visiblePartOfTexture; }
            set { visiblePartOfTexture = value; }
        }

        /// <summary>
        /// Cumulative force on the Sprite (no need to keep track of collection...just the cumulative vector)
        /// </summary>
        private Vector2 forces = new Vector2(0, 0);
        public Vector2 m_forces {
            get { return forces; }
            set { forces = value; }
        }

        /// <summary>
        /// The color to draw the Sprite.  Careful...the fourth parameter to Color is the opacity!
        /// </summary>
        private Color color = Color.White;
        public Color m_color {
            get { return color; }
            set { color = new Color(value.R, value.G, value.B, color.A); }
        }
        /// <summary>
        /// The opacity of the Sprite
        /// </summary>
        public byte m_opacity {
            get { return color.A; }
            set { color = new Color(color.R, color.G, color.B, value); }
        }

        /// <summary>
        /// The angle, in radians, to rotate the sprite around the origin
        /// </summary>
        private float rotation = 0.0f;
        public float m_rotation {
            get { return rotation; }
            set { rotation = value; }
        }

        /// <summary>
        /// The bounding box with which to do collisions
        /// </summary>
        private BoundingBox boundingBox = new BoundingBox();
        public BoundingBox m_boundingBox {
            get { return boundingBox; }
            set { boundingBox = value; }
        }

        /// <summary>
        /// The function that will get called when doCollision is called
        /// </summary>
        private collisionActionDelegate collisionAction = null;
        public collisionActionDelegate m_collisionAction {
            set { collisionAction = value; }
        }

        /// <summary>
        /// The InputHandler that will contain the default keys for moving a Sprite.
        /// (i.e. up, down, left, right)
        /// </summary>
        protected InputHandler inputHandler = new InputHandler();
        public InputHandler m_inputHandler
        {
            set { inputHandler = value; }
            get { return inputHandler; }
        }

        /// <summary>
        /// This is where the texture will be drawn relative to the sprite's position
        /// </summary>
        private Vector2 textureOrigin = new Vector2();
        public Vector2 m_textureOrigin
        {
            set { textureOrigin = value; }
            get { return textureOrigin; }
        }
       
        #endregion

        #region All the Constructors
        public Sprite(Texture2D texture, Vector2 position) {
            m_boundingBox = new BoundingBox(new Vector3(position.X, position.Y, 0), new Vector3()); //((int)position.X, (int)position.Y, texture.Width, texture.Height);
            m_textureOrigin = new Vector2(texture.Width / 2, texture.Height / 2);
            m_visiblePartOfTexture = new Rectangle(0, 0, texture.Width, texture.Height);

            m_texture = texture;
            m_position = position;
        }
        public Sprite(Texture2D texture, Vector2 position, Vector2 velocity) 
            : this(texture, position)
        {
            m_velocity = velocity;
        }
        public Sprite(Texture2D texture, Vector2 position, Vector2 velocity, collisionActionDelegate collisionAction) 
            : this(texture, position, velocity)
        {
            m_collisionAction = collisionAction;
        }
        public Sprite(Texture2D texture, Vector2 position, Vector2 velocity, collisionActionDelegate collisionAction, 
            Vector2 size)
            : this(texture, position, velocity, collisionAction)
        {
            m_size = size;
        }
        public Sprite(Texture2D texture, Vector2 position, Vector2 velocity, collisionActionDelegate collisionAction,
            Vector2 size, Vector2 maxSpeed) 
            : this(texture, position, velocity, collisionAction, size)
        {
            m_maxSpeed = maxSpeed;
        }
        public Sprite(Texture2D texture, Vector2 position, Vector2 velocity, collisionActionDelegate collisionAction,
            Vector2 size, Vector2 maxSpeed, float mass) 
            : this(texture, position, velocity, collisionAction, size, maxSpeed)
        {
            m_mass = mass;
        }
        public Sprite(Texture2D texture, Vector2 position, Vector2 velocity, collisionActionDelegate collisionAction,
            Vector2 size, Vector2 maxSpeed, float mass, float rotation) 
            : this(texture, position, velocity, collisionAction, size, maxSpeed, mass)
        {
            m_rotation = rotation;
        }
        public Sprite(Texture2D texture, Vector2 position, Vector2 velocity, collisionActionDelegate collisionAction,
            Vector2 size, Vector2 maxSpeed, float mass, float rotation, Color color) 
            : this(texture, position, velocity, collisionAction, size, maxSpeed, rotation)
        {
            m_color = color;
        }
        public Sprite(Texture2D texture, Vector2 position, Vector2 velocity, collisionActionDelegate collisionAction,
            Vector2 size, Vector2 maxSpeed, float mass, float rotation, Color color, Vector2 forces) 
            : this(texture, position, velocity, collisionAction, size, maxSpeed, mass, rotation, color)
        {
            m_forces = forces;
        }
        public Sprite(Texture2D texture, Vector2 position, Vector2 velocity, collisionActionDelegate collisionAction,
            Vector2 size, Vector2 maxSpeed, float mass, float rotation, Color color, Vector2 forces, 
            Rectangle visiblePartOfTexture) 
            : this(texture, position, velocity, collisionAction, size, maxSpeed, mass, rotation, color, forces)
        {
            m_visiblePartOfTexture = visiblePartOfTexture;

            m_boundingBox = new BoundingBox(new Vector3(position.X, position.Y, 0), new Vector3(visiblePartOfTexture.Width, visiblePartOfTexture.Height, 0));
            m_textureOrigin = new Vector2(visiblePartOfTexture.Width / 2, visiblePartOfTexture.Height / 2);
        }
        #endregion

        #region All other Functions
        /// <summary>
        /// Draws the visible portion of the sprite's texture at its position, with its size, color, and rotation
        /// </summary>
        public void Draw(SpriteBatch spriteBatch) {
            spriteBatch.Draw(texture, new Rectangle((int)position.X, (int)position.Y, (int)(visiblePartOfTexture.Width * size.X), (int)(visiblePartOfTexture.Height * size.Y)), visiblePartOfTexture, color, rotation, textureOrigin, SpriteEffects.None, 1);
        }

        public void Draw(SpriteBatch spriteBatch, Vector2 position)
        {
            spriteBatch.Draw(texture, new Rectangle((int)position.X, (int)position.Y, (int)(visiblePartOfTexture.Width * size.X), (int)(visiblePartOfTexture.Height * size.Y)), visiblePartOfTexture, color, rotation, textureOrigin, SpriteEffects.None, 1);
        }

        /// <summary>
        /// Adds the given force vector to the current force vector of the Sprite
        /// </summary>
        public void AddForce(Vector2 force) {
            m_forces += force;
        }

        /// <summary>
        /// Updates the sprite by adding the forces to the velocity vector and then moving the sprite
        /// </summary>
        public void Update(GameTime gameTime, bool checkInput, bool updateBoundingBox) {
            m_velocity += m_forces;
            if (m_velocity.X > m_maxSpeed.X)
                velocity.X = m_maxSpeed.X;
            if (m_velocity.Y > m_maxSpeed.Y)
                velocity.Y = m_maxSpeed.Y;
            m_position += m_velocity;

            if (checkInput)
                this.checkInput();
            if (updateBoundingBox)
            {
                UpdateBoundingBox();
            }
        }

        public void UpdateBoundingBox()
        {
            m_boundingBox = new BoundingBox(new Vector3(position.X, position.Y, 0), new Vector3(visiblePartOfTexture.Width, visiblePartOfTexture.Height, 0));
        }

        /// <summary>
        /// Determines if this Sprite collides with the given Bounding Box
        /// </summary>
        public bool CollidesWith(BoundingBox bb) {
            return boundingBox.Intersects(bb);
        }

        /// <summary>
        /// Function to call to perform the collision action
        /// </summary>
        public void doCollision(Sprite sprite2) {
            collisionAction(this, sprite2);
        }

        /// <summary>
        /// Goes through and sees if the assigned buttons are being pressed.
        /// </summary>
        public void checkInput() {
            inputHandler.CheckInputs();
        }

        /// <summary>
        /// Moves the Sprite by the given x and y offsets.
        /// <param name="offset">The vector containing the x and y offsets.</param>
        /// </summary>
        public void Move(Vector2 offset) {
            m_position += offset;
        }

        /// <summary>
        /// This function should be bound in the input handler to whatever button should move 
        /// the Sprite up(i.e. up arrow).  It will then be called when the button is activated.
        /// <param name="state">The state of the button (pressed or released)</param>
        /// </summary>
        private void moveUp(Keys key, KeyState state) {
            if (state == KeyState.Down)
                m_velocityY -= m_moveSpeed;
        }
        private void moveUp(Buttons button, ButtonState state, PlayerIndex player)
        {
            if (state == ButtonState.Pressed)
                m_velocityY -= m_moveSpeed;
        }
        private void moveUp(MouseButtons button, ButtonState state)
        {
            if (state == ButtonState.Pressed)
                m_velocityY -= m_moveSpeed;
        }

        /// <summary>
        /// This function should be bound in the input handler to whatever button should move 
        /// the Sprite down(i.e. down arrow).  It will then be called when the button is activated.
        /// <param name="state">The state of the button (pressed or released)</param>
        /// </summary>
        private void moveDown(Keys key, KeyState state)
        {
            if (state == KeyState.Down)
                m_velocityY += m_moveSpeed;
        }
        private void moveDown(Buttons button, ButtonState state, PlayerIndex player)
        {
            if (state == ButtonState.Pressed)
                m_velocityY += m_moveSpeed;
        }
        private void moveDown(MouseButtons button, ButtonState state)
        {
            if (state == ButtonState.Pressed)
                m_velocityY += m_moveSpeed;
        }

        /// <summary>
        /// This function should be bound in the input handler to whatever button should move 
        /// the Sprite left(i.e. left arrow).  It will then be called when the button is activated.
        /// <param name="state">The state of the button (pressed or released)</param>
        /// </summary>
        private void moveLeft(Keys key, KeyState state)
        {
            if (state == KeyState.Down)
                m_velocityX -= m_moveSpeed;
        }
        private void moveLeft(Buttons button, ButtonState state, PlayerIndex player)
        {
            if (state == ButtonState.Pressed)
                m_velocityX -= m_moveSpeed;
        }
        private void moveLeft(MouseButtons button, ButtonState state)
        {
            if (state == ButtonState.Pressed)
                m_velocityX -= m_moveSpeed;
        }

        /// <summary>
        /// This function should be bound in the input handler to whatever button should move 
        /// the Sprite right(i.e. right arrow).  It will then be called when the button is activated.
        /// <param name="state">The state of the button (pressed or released)</param>
        /// </summary>
        private void moveRight(Keys key, KeyState state)
        {
            if (state == KeyState.Down)
                m_velocityX += m_moveSpeed;
        }
        private void moveRight(Buttons button, ButtonState state, PlayerIndex player)
        {
            if (state == ButtonState.Pressed)
                m_velocityX += m_moveSpeed;
        }
        private void moveRight(MouseButtons button, ButtonState state)
        {
            if (state == ButtonState.Pressed)
                m_velocityX += m_moveSpeed;
        }

        /// <summary>
        /// Assigns the given button to the given direction of movement.
        /// </summary>
        public void AssignButtonToMovement(Keys keyboardButton, MoveDirection direction) {
            // Make sure to check that m_inputHandler is initialized before trying to add to it.
            switch (direction)
            {
                case MoveDirection.UP:
                    inputHandler.Bind(keyboardButton, (InputHandler.KeyboardInputAction)moveUp, 0);
                    break;

                case MoveDirection.DOWN:
                    inputHandler.Bind(keyboardButton, (InputHandler.KeyboardInputAction)moveDown, 0);
                    break;

                case MoveDirection.LEFT:
                    inputHandler.Bind(keyboardButton, (InputHandler.KeyboardInputAction)moveLeft, 0);
                    break;

                case MoveDirection.RIGHT:
                    inputHandler.Bind(keyboardButton, moveRight, 0);
                    break;
            }
        }
        public void AssignButtonToMovement(Buttons gamePadButton, MoveDirection direction) {
            // Make sure to check that m_inputHandler is initialized before trying to add to it.
            switch (direction)
            {
                case MoveDirection.UP:
                    inputHandler.Unbind(gamePadButton, (InputHandler.ButtonInputAction)moveUp);
                    break;

                case MoveDirection.DOWN:
                    inputHandler.Unbind(gamePadButton, (InputHandler.ButtonInputAction)moveDown);
                    break;

                case MoveDirection.LEFT:
                    inputHandler.Unbind(gamePadButton, (InputHandler.ButtonInputAction)moveLeft);
                    break;

                case MoveDirection.RIGHT:
                    inputHandler.Unbind(gamePadButton, (InputHandler.ButtonInputAction)moveRight);
                    break;
            }
        }
        public void AssignButtonToMovement(MouseButtons mouseButton, MoveDirection direction) {
            // Make sure to check that m_inputHandler is initialized before trying to add to it.
            switch (direction)
            {
                case MoveDirection.UP:
                    inputHandler.Unbind(mouseButton, (InputHandler.MouseButtonInputAction)moveUp);
                    break;

                case MoveDirection.DOWN:
                    inputHandler.Unbind(mouseButton, (InputHandler.MouseButtonInputAction)moveDown);
                    break;

                case MoveDirection.LEFT:
                    inputHandler.Unbind(mouseButton, (InputHandler.MouseButtonInputAction)moveLeft);
                    break;

                case MoveDirection.RIGHT:
                    inputHandler.Unbind(mouseButton, (InputHandler.MouseButtonInputAction)moveRight);
                    break;
            }
        }
        /// <summary>
        /// Removes the given button from the given direction of movement
        /// </summary>
        public void RemoveButtonFromMovement(Keys keyboardButton, MoveDirection direction) {
            switch (direction)
            {
                case MoveDirection.UP:
                    inputHandler.Unbind(keyboardButton, (InputHandler.KeyboardInputAction)moveUp);
                    break;

                case MoveDirection.DOWN:
                    inputHandler.Unbind(keyboardButton, (InputHandler.KeyboardInputAction)moveDown);
                    break;

                case MoveDirection.LEFT:
                    inputHandler.Unbind(keyboardButton, (InputHandler.KeyboardInputAction)moveLeft);
                    break;

                case MoveDirection.RIGHT:
                    inputHandler.Unbind(keyboardButton, (InputHandler.KeyboardInputAction)moveRight);
                    break;
            }
        }
        public void RemoveButtonFromMovement(Buttons gamePadButton, MoveDirection direction) {
            throw new System.NotImplementedException();
        }
        public void RemoveButtonFromMovement(MouseButtons mouseButton, MoveDirection direction) {
            throw new System.NotImplementedException();
        }
        #endregion
    }
}
